package com.zipking.cloud.springbootmybatis.persistance.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.zipking.cloud.springbootmybatis.persistance.mapper.MyBaseMapper;
import com.zipking.cloud.springbootmybatis.persistance.service.IMyService;
import com.zipking.cloud.springbootmybatis.util.SplitListUtilsExt;
import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.ExecutorType;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionHolder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionSynchronizationManager;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.function.BiFunction;
import java.util.stream.Collectors;

@Slf4j
public class MyServiceImpl<M extends MyBaseMapper<T>, T>extends ServiceImpl<M,T> implements IMyService<T> {
    @Override
    public int insertBatchSomeColumn(List<T> entityList) {
        return this.baseMapper.insertBatchSomeColumn(entityList);
    }

    @Override
    public int insertBatchSomeColumn(List<T> entityList, int batchSize) {
        int size=entityList.size();
        if(size<batchSize){
            return this.baseMapper.insertBatchSomeColumn(entityList);
        }


        int fromIndex = 0;
        int toIndex = Math.min(batchSize, entityList.size());

        while (fromIndex < entityList.size()) {
            List<T> batch = entityList.subList(fromIndex, toIndex);
            int count= baseMapper.insertBatchSomeColumn(batch);
            log.info("本次插入:{}条",count);
            fromIndex = toIndex;
            toIndex = Math.min(fromIndex + batchSize, entityList.size());
        }
     /*   int page=1;
        if(size % batchSize ==0){
            page=size/batchSize;
        }else {
            page=size/batchSize+1;
        }
        for (int i = 0; i < page; i++) {
            List<T> sub = new ArrayList<>();
            if(i==page-1){
                sub=entityList.subList(i*batchSize, entityList.size());
            }else {
                sub.subList(i*batchSize,(i+1)*batchSize);
            }
            if(sub.size()>0){
                baseMapper.insertBatchSomeColumn(sub);
            }

        }*/
        return size;
    }


    // 每次处理的最大条目数
    private static final int BATCH_SIZE = 500;

    @Resource
    private SqlSessionFactory sqlSessionFactory;

    /**
     * 对数据列表进行批量更新或插入操作。
     * <p>
     * 如果当前处于 Spring 管理的事务中，则该操作会在该事务中执行。
     * 如果当前没有处于 Spring 管理的事务中，则该操作会在新的事务中执行。
     *
     * @param data        需要被处理的数据列表
     * @param mapperClass MyBatis 的 Mapper 类
     * @param function    用于定义如何处理每一个数据项的函数
     * @return 影响的总行数
     */
    @Transactional(rollbackFor = Exception.class)
    public <T, U, R> int batchUpdateOrInsert(List<T> data, Class<U> mapperClass, BiFunction<T, U, R> function) {
        if (data == null || data.isEmpty() || mapperClass == null) {
            throw new IllegalArgumentException("Invalid input parameters. Cannot be null or empty");
        }

        SqlSessionHolder sqlSessionHolder = (SqlSessionHolder) TransactionSynchronizationManager.getResource(sqlSessionFactory);
        boolean transaction = TransactionSynchronizationManager.isSynchronizationActive();
        if (sqlSessionHolder != null) {
            SqlSession sqlSession = sqlSessionHolder.getSqlSession();
            //原生无法支持执行器切换，当存在批量操作时，会嵌套两个session的，优先commit上一个session
            //按道理来说，这里的值应该一直为false。
            sqlSession.commit(!transaction);
        }

        int totalAffectedRows = 0;

        SqlSession batchSqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH);
        if (!transaction) {
            log.warn("SqlSession [" + batchSqlSession + "] Transaction not enabled");
        }

        try {
            U mapper = batchSqlSession.getMapper(mapperClass);

            for (int i = 0; i < data.size(); i++) {
                function.apply(data.get(i), mapper);

                // 每处理 BATCH_SIZE 条数据，或者已经处理到数据列表的最后一项，就执行一次刷新
                if (i != 0 && (i % BATCH_SIZE == 0 || i == data.size() - 1)) {
                    batchSqlSession.flushStatements();
                }

                totalAffectedRows++;
            }
            batchSqlSession.flushStatements();
            // 如果当前没有处于 Spring 管理的事务中，那么这个 commit 就会执行。否则，这个 commit 是无效的，将由 Spring 来执行实际的 commit。
            batchSqlSession.commit(!transaction);
        } catch (Exception e) {
            batchSqlSession.rollback();
            throw e;
        } finally {
            batchSqlSession.close();
        }

        return totalAffectedRows;
    }
    @Override
    public boolean saveOrUpdateBatch(Collection<T> entityList, int batchSize) {
        if (CollUtil.isEmpty(entityList)) {
            return false;
        }
        List<T> updates = entityList.stream().filter(x -> !isIdNull(x)).collect(Collectors.toList());
        List<T> inserts = entityList.stream().filter(x -> isIdNull(x)).collect(Collectors.toList());
        int count = 0;
        List<T> tmpList = new ArrayList<>();
        if (CollUtil.isNotEmpty(inserts)) {
            for (T insert : inserts) {
                int i = tmpList.size();
                if (i >= 1 && i % batchSize == 0) {
                    count += getBaseMapper().insertBatchSomeColumn(tmpList);
                    tmpList.clear();
                }
                tmpList.add(insert);
            }
            count += getBaseMapper().insertBatchSomeColumn(tmpList);
            tmpList.clear();
        }
        if (CollUtil.isNotEmpty(updates)) {
            for (T update : updates) {
                int i = tmpList.size();
                if (i >= 1 && i % batchSize == 0) {
                    count += getBaseMapper().updateBatch(tmpList);
                    tmpList.clear();
                }
                tmpList.add(update);
            }
            count += getBaseMapper().updateBatch(tmpList);
            tmpList.clear();
        }
        return count > 0;
    }
    private boolean isIdNull(Object obj) {
        return Objects.isNull(BeanUtil.getProperty(obj, "id"));
    }
}
